/*
 * @Description:  编译节点
 * @Author: YiHe
 * @Date: 2020-08-20 14:47:34
 * @LastEditors: YiHe
 * @LastEditTime: 2020-08-20 23:09:51
 */
import Watcher from './watcher.js'
class Compile {
	constructor(dom, vm) {
		this.$el = document.querySelector(dom)
		this.$vm = vm
		this.$virtualEl = this.createVirtualEl(this.$el)
		this.compileEl(this.$virtualEl)
		this.$el.appendChild(this.$virtualEl)
	}

	createVirtualEl(el) {
		const virtualEl = document.createDocumentFragment()
		let child
		while (child = el.firstChild) {
			virtualEl.appendChild(child)
		}
		return virtualEl
	}

	compileEl(el) {
		Object.values(el.childNodes).forEach(node => {
			const text = node.textContent
			var reg = /\{\{(.*)\}\}/
			if (this.isElementNode(node)) {
				this.compile(node)
			}
			else if (this.isTextNode(node) && reg.test(text)) {
				this.compileText(node, RegExp.$1.trim())
			}
			if (node.childNodes && node.childNodes.length) {
				this.compileEl(node)
			}
		})
	}

	compile(node) {
		const nodeAttrs = node.attributes
		Object.values(nodeAttrs).forEach(attr => {
			const attrName = attr.name
			if (this.isDirective(attrName)) {
				const exp = attr.value
				const dir = attrName.substring(2)
				if (this.isEventDirective(dir)) {
					compileUtil.eventHandler(node, this.$vm, exp, dir)
				} else {
					compileUtil[dir] && compileUtil[dir](node, this.$vm, exp)
				}
				node.removeAttribute(attrName)
			}
		})
	}

	compileText(node, exp) {
		compileUtil.text(node, this.$vm, exp)
	}

	isElementNode(node) {
		return node.nodeType == 1
	}

	isTextNode(node) {
		return node.nodeType == 3
	}

	isDirective(attr) {
		return attr.indexOf('v-') == 0
	}

	isEventDirective(dir) {
		return dir.indexOf('on') === 0
	}

}
// 指令处理集合
const compileUtil = {
	text: function (node, vm, exp) {
		this.bind(node, vm, exp, 'text')
	},

	html: function (node, vm, exp) {
		this.bind(node, vm, exp, 'html')
	},

	model: function (node, vm, exp) {
		this.bind(node, vm, exp, 'model')
		node.addEventListener('input', e => {
			const val = this._getVMVal(vm, exp)
			const newValue = e.target.value
			if (val === newValue) return
			this_setVMVal(vm, exp, newValue)
			val = newValue
		})
	},

	class: function (node, vm, exp) {
		this.bind(node, vm, exp, 'class')
	},

	bind: function (node, vm, exp, dir) {
		const updaterFn = updater[dir + 'Updater']
		updaterFn && updaterFn(node, this._getVMVal(vm, exp))
		new Watcher(vm, exp, function (value, oldValue) {
			updaterFn && updaterFn(node, value, oldValue)
		})
	},

	eventHandler: function (node, vm, exp, dir) {
		const eventType = dir.split(':')[1]
		const fn = vm.options.methods && vm.options.methods[exp]
		if (eventType && fn) {
			node.addEventListener(eventType, fn.bind(vm), false)
		}
	},

	_getVMVal: function (vm, exp) {
		return vm[exp]
	},

	_setVMVal: function (vm, exp, value) {
		vm[exp] = value
	}
}
var updater = {
	textUpdater: function (node, value) {
		node.textContent = typeof value == 'undefined' ? '' : value
	},

	htmlUpdater: function (node, value) {
		node.innerHTML = typeof value == 'undefined' ? '' : value
	},

	classUpdater: function (node, value, oldValue) {
		let className = node.className
		className = className.replace(oldValue, '').replace(/\s$/, '')
		const space = className && String(value) ? ' ' : ''
		node.className = className + space + value
	},

	modelUpdater: function (node, value, oldValue) {
		node.value = typeof value == 'undefined' ? '' : value
	}
};
export default Compile